﻿using System.Collections.Generic;
using UnityEngine;

namespace ZFramework.Runtime
{
    public class AssetLoader<T> : IReference where T : Object
    {
        private static int _serial;
        private Dictionary<string, List<AssetLoaderCallbackInfo>> _loadCallbackInfos;
        private List<AssetLoaderCallbackInfo> _cachedCallbackInfos;
        private Dictionary<int, string> _uuid2Path;

        private IEventManager _eventMgr;

        private IAssetLoaderMgr _assetLoaderMgr;
        
        public AssetLoader()
        {
            _uuid2Path = new Dictionary<int, string>();
            _loadCallbackInfos = new Dictionary<string, List<AssetLoaderCallbackInfo>>();
            _cachedCallbackInfos = new List<AssetLoaderCallbackInfo>();
        }

        public void Clear()
        {
            _loadCallbackInfos.Clear();
            _uuid2Path.Clear();
            _eventMgr.Unsubscribe(AssetLoadSuccessEventArgs.EventId, OnLoadAssetSuccess);
        }

        public void Release()
        {
            foreach (var item in _uuid2Path)
            {
                var path = item.Value;
                _assetLoaderMgr.UnloadAsset(path);
            }
            _uuid2Path.Clear();
            _eventMgr.Unsubscribe(AssetLoadSuccessEventArgs.EventId, OnLoadAssetSuccess);
        }

        public static AssetLoader<T> Create(IAssetLoaderMgr assetLoaderMgr, IEventManager eventMgr)
        {
            var loader = ReferencePool.Acquire<AssetLoader<T>>();
            loader._assetLoaderMgr = assetLoaderMgr;
            loader._eventMgr = eventMgr;
            eventMgr.Subscribe(AssetLoadSuccessEventArgs.EventId, loader.OnLoadAssetSuccess);
            return loader;
        }

        public int LoadAssetAsync(string path, AssetLoaderCallback callback, object userData)
        {
            var serialId = _serial++;
            _uuid2Path[serialId] = path;
            if (!_loadCallbackInfos.TryGetValue(path, out var loadCallback))
            {
                loadCallback = new List<AssetLoaderCallbackInfo>();
                _loadCallbackInfos[path] = loadCallback;
            }

            var callbackInfo = AssetLoaderCallbackInfo.Create(callback, userData, serialId);
            loadCallback.Add(callbackInfo);
            _assetLoaderMgr.LoadAssetAsync<T>(path);
            return serialId;
        }

        public void UnLoadAsset(int uuid)
        {
            if (!_uuid2Path.TryGetValue(uuid, out var path))
            {
                return;
            }

            _uuid2Path.Remove(uuid);
            if (!_loadCallbackInfos.TryGetValue(path, out var callbackInfos))
            {
                return;
            }

            foreach (var callbackInfo in callbackInfos)
            {
                if (uuid == callbackInfo.Uuid)
                {
                    callbackInfos.Remove(callbackInfo);
                    break;
                }
            }
        }

        private void OnLoadAssetSuccess(object sender, GameEventArgs e)
        {
            var args = e as AssetLoadSuccessEventArgs;
            if (args == null)
                return;

            var path = args.Path;
            var asset = args.Asset;
            if (!_loadCallbackInfos.TryGetValue(path, out var loadCallback))
            {
                return;
            }
            _cachedCallbackInfos.Clear();
            foreach (var callbackInfo in loadCallback)
            {
                var callback = callbackInfo.Callback;
                callback?.Invoke(true, path, asset, callbackInfo.UserData);
                _cachedCallbackInfos.Add(callbackInfo);
            }
            loadCallback.Clear();

            foreach (var callbackInfo in _cachedCallbackInfos)
            {
                ReferencePool.Release(callbackInfo);
            }
        }
    }
}